%%%-------------------------------------------------------------------
%%% @author Rain
%%% @doc yaml配置动态转proto文件
%%% @end
%%%-------------------------------------------------------------------
-module(yaml2proto).
-author("Rain").
-include("build_proto.hrl").

%% API
-export([build/4]).

%% @doc 将yaml文件转换成proto文件
build(YamlPath, ProtoOutPath, ProtoCmdOutPath, ProtoVersion) ->
    build_proto:erase_all_proc_dict(),
    {'ok', _} = application:ensure_all_started(yamerl),
    Pattern = filename:join(YamlPath, "*.yaml"),
    Files = filelib:wildcard(Pattern),
    parse_file_and_build(Files, ProtoVersion, ProtoOutPath),
    build_proto_cmd:build(ProtoCmdOutPath),
    ok.

parse_file_and_build([], _ProtoVersion, _ProtoOutPath) ->
    ok;
parse_file_and_build([File | T], ProtoVersion, ProtoOutPath) ->
    [YamlData] = yamerl:decode_file(File),
    try
        YamlDataMaps = maps:from_list(YamlData),
        ProtoName = filename:basename(File, ".yaml"),
        BuildProtocol = parse_file_and_build1(YamlDataMaps, ProtoName),
        build_proto:build(BuildProtocol, ProtoOutPath, ProtoVersion)
    catch
        Class:Reason:Stack ->
            io:format("Class ~p Reason ~p ~n~n Stack ~p ~n~n", [Class, Reason, Stack]),
            halt(1)
    end,
    parse_file_and_build(T, ProtoVersion, ProtoOutPath).

parse_file_and_build1(YamlDataMaps, ProtoName) ->
    Imports = maps:get("import", YamlDataMaps, []),
    Messages = maps:get("messages", YamlDataMaps, []),
    CmdMainId = maps:get("cmd_main_id", YamlDataMaps, 0),
    Structs = maps:get("structs", YamlDataMaps, []),
    BuildProtocol = #build_proto{cmd_main_id = CmdMainId,
        proto_name = ProtoName, imports = Imports},
    BuildProtocol1 = parse_msg(lists:reverse(Messages), BuildProtocol),
    BuildProtocol2 = parse_struct(lists:reverse(Structs), BuildProtocol1),
    BuildProtocol2.

parse_msg([], BuildProtocol) ->
    BuildProtocol;
parse_msg([Msg | T], #build_proto{} = BuildProtocol) ->
    MsgMap = maps:from_list(Msg),
    BuildProtocol1 = do_parse_msg(MsgMap, BuildProtocol),
    parse_msg(T, BuildProtocol1).

do_parse_msg(#{
    "cmd_sub_id" := CmdSubId,
    "name" := Name,
    "desc" := Desc} = MsgMap, #build_proto{messages = Messages} = BuildProtocol) ->
    Add = #build_proto_msg{cmd_sub_id = CmdSubId,
        name = Name,
        desc = Desc},
    Add1 =
        case MsgMap of
            #{"c2s" := C2SMsg} ->
                Add#build_proto_msg{c2s = C2SMsg};
            _ ->
                Add
        end,
    Add2 =
        case MsgMap of
            #{"s2c" := S2CMsg} ->
                Add1#build_proto_msg{s2c = S2CMsg};
            _ ->
                Add1
        end,
    Messages1 = [Add2 | Messages],
    BuildProtocol#build_proto{messages = Messages1}.

parse_struct([], BuildProtocol) ->
    BuildProtocol;
parse_struct([Msg | T], #build_proto{} = BuildProtocol) ->
    StructMap = maps:from_list(Msg),
    BuildProtocol1 = do_parse_struct(StructMap, BuildProtocol),
    parse_struct(T, BuildProtocol1).

do_parse_struct(#{
    "name" := Name,
    "desc" := Desc,
    "fields" := Fields}, #build_proto{structs = Structs} = BuildProtocol) ->
    Add = #build_proto_struct{fields = Fields,
        name = Name,
        desc = Desc},
    Structs1 = [Add | Structs],
    BuildProtocol#build_proto{structs = Structs1}.
